
#include <BPair>
#include <BStream>
#include "member_BXmlDocument.h"
#include "member_BXmlNode.h"

using namespace BWE;

member_BXmlDocument::member_BXmlDocument(BXmlDocument* xmldocument)
{
	boss = xmldocument;
	lineNumber = 0;
	linePos = 0;
}
member_BXmlDocument::~member_BXmlDocument()
{

}

void member_BXmlDocument::makeFormat(BXmlNode* node)
{
	member_BXmlNode* sonmem = xml_member(node);
	sonmem->format.toString();
	if (doctype == "html" && node->name() == "pre")
	{
		sonmem->format.head_post = 0;
		sonmem->format.text_prev = 0;
		sonmem->format.text_post = 0;
		sonmem->format.tail_prev = 0;
	}

	BArray<BXmlNodeHolder>& children = sonmem->children;
	for (int i = 0; i < children.size(); i++)
	{
		BXmlNode* child = children[i];
		makeFormat(child);
	}
}
const XmlFormat* member_BXmlDocument::nodeFormat(const BXmlNode* node)
{
	member_BXmlNode* sonmem = xml_member(node);
	if (!sonmem->format.text.empty())
	{
		if (doctype == "html" && node->name() == "pre")
		{
			sonmem->format.head_post = 0;
			sonmem->format.text_prev = 0;
			sonmem->format.text_post = 0;
			sonmem->format.tail_prev = 0;
		}
		return &sonmem->format;
	}
	return &format;
}

int member_BXmlDocument::readTagName(const BString& text, BString& tagName)
{
	if (text.empty())
		return 0;
	int begin = 1;
	if (text[1] == '/')
		begin = 2;
	int end = begin;
	while (end < text.size())
	{
		if (isspace(text[end]))
			break;
		if (text[end] == '/')
			break;
		if (text[end] == '>')
			break;
		end++;
	}
	tagName.reset(text, begin, end);
	return end;
}
void member_BXmlDocument::readAttribs(BXmlNode* node, BString& text)
{
	if (text.empty())
		return;
	text.simplify();
	BString label;
	BString value;
	int begin = -1;
	int end = 0;
	while (end < text.size())
	{
		if (text[end] == '\"')
		{
			if (begin < 0)
			{
				begin = end++;
				continue;
			}
			text.substr(value, begin + 1, end);
			text.substr(label, 0, begin);
			label.trimmed();
			if (label.last() != '=')
				return;
			label.remove('=');
			label.trimmed();
			node->setAttrib(label, value);
			text.remove(0, end + 1);
			begin = -1;
			end = 0;
		}
		end++;
	}
}
void member_BXmlDocument::readDoctype(BXmlNode* node, BString& text)
{
	node->setText(text);
	text.trimmed();
	text.lower();
	doctype = text;
}
bool member_BXmlDocument::convertText(const BString& name, BString& text)
{
	if (text.empty())
		return false;
	if (doctype != "html" || name != "pre")
		text.simplify();
	text.replace("&amp;", '&');
	text.replace("&lt;", '<');
	text.replace("&gt;", '>');
	if (text.empty() || text == ' ')
		return false;
	BString temp;
	int pos = text.find("&#");
	while (pos < text.size())
	{
		int end = text.find(';', pos);
		if (end < text.size())
		{
			text.substr(temp, pos + 2, end);
			text.remove(pos + 1, end - pos);
			int key = temp.toInt();
			if (key == 0x0009)
				text[pos++] = '\t';
			else if (key == 0x000A)
				text[pos++] = '\n';
			else if (key == 0x000D)
				text[pos++] = '\r';
			else if (key == 0x0020)
				text[pos++] = ' ';
		}
		pos = text.find("&#", pos);
	}
	return true;
}

bool member_BXmlDocument::readComment(BXmlNode* node, const BString& sontext, int& pos)
{
	if (node->name() == "!--")
	{
		int begin = pos;
		char linebreak = 0;
		int end = sontext.find("-->", pos);
		while (pos < end)
		{
			char code = sontext[pos++];
			if (code == '\n' && pos > linePos)
			{
				lineNumber++;
				linePos = pos;
			}
		}
		sontext.substr(xml_member(node)->text, begin, pos - 2);
		pos += 3;
		return true;
	}
	return false;
}
bool member_BXmlDocument::readNode(BXmlNode* node, const BString& xmltext, int& pos)
{
	BString text;
	char linebreak = 0;
	int tag_head = -1;
	int tag_tail = -1;
	char code = 0;
	while (pos < xmltext.size())
	{
		code = xmltext[pos];
		text.append(code);
		linePos++;
		pos++;
		if (code == '\n')
		{
			linebreak = '\n';
			lineNumber++;
			linePos = 0;
			continue;
		}
		if (code == '-' && text.endWith("<!--"))
		{
			BXmlNode* child = new BXmlNode("!--", node);
			xml_member(child)->document = boss;
			readComment(child, xmltext, pos);
			text.reset();
			continue;
		}
		if (code == '\"')
		{
			int end = xmltext.find('\"', pos);
			text.append(xmltext, pos, end);
			pos = end;
			if (end < xmltext.size())
			{
				text.append('\"');
				pos++;
			}
			continue;
		}
		if (code == '<')
		{
			tag_head = text.size() - 1;
		}
		if (code == '>')
		{
			tag_tail = text.size();
		}
		if (tag_tail > tag_head)
		{
			BString tagText;
			text.substr(tagText, tag_head, tag_tail);
			text.remove(tag_head, tag_tail);

			BString tagName;
			int name_end = readTagName(tagText, tagName);
			if (tagText.beginWith("</"))
			{
				const BString& name = node->name();
				if (tagName != name)
					return false;
				xml_member(node)->format.tail_prev = linebreak;
				linebreak = 0;
				if (convertText(name, text))
				{
					if (node->childCount())
					{
						node->addText(text);
					}
					else
					{
						node->setText(text);
						xml_member(node)->format.head_post = linebreak;
						linebreak = 0;
					}
					text.reset();
				}
				xml_member(node)->format.tail_alone = true;
				break;
			}
			if (tagText.endWith("/>"))
			{
				BXmlNode* child = new BXmlNode(tagName);
				xml_member(child)->document = boss;
				xml_member(child)->format.head_prev = linebreak;
				linebreak = 0;
				tagText -= "/>";
				tagText.remove(0, name_end);
				readAttribs(child, tagText);
				if (convertText(tagName, text))
				{
					if (node->childCount())
					{
						node->addText(text);
					}
					else
					{
						node->setText(text);
						xml_member(child)->format.head_post = linebreak;
						linebreak = 0;
					}
					text.reset();
				}
				node->addChild(child);
				xml_member(child)->format.tail_prev = linebreak;
				linebreak = 0;
			}
			else
			{
				if (tag_head > 0)
				{
					text.simplify();
					if (text.size() && convertText(tagName, text))
					{
						if (node->childCount())
							node->addText(text);
						else
							node->setText(text);
					}
					text.clear();
					tag_head = 0;
				}
				BXmlNode* child = node->addChild(tagName);
				tagText -= '>';
				tagText.remove(0, name_end);
				readAttribs(child, tagText);
				int old_pos = pos;
				if (readNode(child, xmltext, pos))
				{
					xml_member(child)->format.head_prev = linebreak;
					linebreak = 0;
				}
				else
				{
					pos = old_pos;
				}
			}
			tag_head = -1;
			tag_tail = -1;
		}
	}

	text.simplify();
	if (text.size() && convertText(node->name(), text))
	{
		if (node->childCount())
			node->addText(text);
		else
			node->setText(text);
	}

	return true;
}
void member_BXmlDocument::writeNode(const BXmlNode* node, BString& xmltext, int tab)
{
	const XmlFormat* fmt = nodeFormat(node);
	member_BXmlNode* mem = xml_member(node);
	if (fmt->head_prev)
	{
		if (!xmltext.endWith('\n'))
			xmltext << '\n';
		xmltext.append('\t', tab);
	}
	const BString* text = &mem->text;
	for (int i = 0; i < mem->text.size(); i++)
	{
		char c = mem->text[i];
		if (c == '&' || c == '<' || c == '>')
		{
			convText = mem->text;
			convText.replace('&', "&amp;");
			convText.replace('<', "&lt;");
			convText.replace('>', "&gt;");
			text = &convText;
			break;
		}
	}
	if (node->name().empty())
	{
		if (text->size())
			xmltext << *text;
		if (fmt->tail_post)
			xmltext << '\n';
		return;
	}
	xmltext << '<' << node->name();
	if (mem->attribMap.size())
	{
		for (auto it = mem->attribMap.begin(); it.valid(); ++it)
		{
			xmltext << ' ' << it.key() << '=' << '\"' << it.value() << '\"';
		}
	}
	if (!mem->format.tail_alone && mem->text.empty() && mem->children.empty())
	{
		xmltext << "/>";
		if (fmt->tail_post)
			xmltext << '\n';
		return;
	}
	xmltext << '>';
	if (fmt->head_post)
		xmltext << '\n';
	if (mem->text.size())
	{
		if (doctype == "html" && node->name() == "pre")
		{
			xmltext << *text;
		}
		else if (fmt->text_prev)
		{
			int pos = 0;
			while (pos < text->size())
			{
				if (!xmltext.endWith('\n'))
					xmltext << '\n';
				int end = text->find('\n', pos);
				xmltext.append('\t', tab + 1);
				xmltext.append(text->cstr(), pos, end);
				pos = end + 1;
			}
		}
		else
		{
			if (xmltext.endWith('\n'))
				xmltext.append('\t', tab + 1);
			xmltext << *text;
		}
		if (fmt->text_post)
		{
			xmltext << '\n';
		}
	}
	if (mem->children.size())
	{
		for (int i = 0; i < node->childCount(); i++)
		{
			const BXmlNode* child = node->child(i);
			writeNode(child, xmltext, tab + 1);
		}
	}
	if (fmt->tail_prev)
	{
		if (!xmltext.endWith('\n'))
			xmltext << '\n';
		xmltext.append('\t', tab);
	}
	xmltext << "</" << node->name() << ">";
	if (fmt->tail_post)
		xmltext << '\n';
}
